/*
 * Copyright (C) 2013-2014 Lannocc Technologies
 * @%@~LICENSE~@%@
 */
package com.iovar.web.dev.trans;

// local imports:
import com.iovar.web.*;
import com.iovar.web.bin.shell.task.*;
import com.iovar.web.dev.*;

// java imports:
import java.io.*;
import java.io.File;
import java.nio.file.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

// 3rd-party imports:

/**
 * Loopback to the local HTTP interface relative to the webapp root.
 *
 * @author  shawn@lannocc.com
 */
public class Local extends Transport
{
    public Local ()
    {
        
    }
    
    public Local (final String path, final ServletContext context)
    {
        this (path, context, new HashMap<String,List<String>> ());
    }
    
    public Local (final String path, final ServletContext context, Map<String,List<String>> params)
    {
        setContext (context);
        setPath (path);
        setParams (params);
    }
    
    public InputStream get () throws IOException
    {
        return new Http (this).get ();
    }
    
    public InputStream put (final InputStream data) throws IOException
    {
        return new Http (this).put (data);
    }
    
    public InputStream patch (final InputStream data, final String contentType) throws IOException
    {
        return new Http (this).patch (data, contentType);
    }
    
    public InputStream delete () throws IOException
    {
        return new Http (this).delete ();
    }
    
    public Return post (final InputStream data, final String contentType) throws IOException
    {
        return new Http (this).post (data, contentType);
    }
    
    public boolean exists () throws IOException
    {
        return new Http (this).exists ();
    }
    
    public static class Servlet extends HttpServlet
    {
        protected void service (final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException
        {
            if ("PATCH".equalsIgnoreCase(req.getMethod ()))
            {
                doPatch (req, resp);
            }
            else
            {
                super.service (req, resp);
            }
        }
        
        protected void doHead (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
        {
            final String path = req.getPathInfo ();
            Log.debug ("local HEAD: "+path);
        
            final ServletContext context = getServletContext ();
            
            final Transport resource = getTransport (context, Utils.getParams (req.getQueryString ()), path);
            Log.debug ("resource: "+resource);
            
            if (! resource.exists ())
            {
                Log.debug ("does not exist: "+resource);
                resp.sendError (resp.SC_NOT_FOUND, "does not exist: "+path);
                return;
            }
        }
        
        protected void doGet (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
        {
            String path = req.getPathInfo ();
            Log.debug ("local GET: "+path);
        
            final ServletContext context = getServletContext ();
            
            final Transport resource = getTransport (context, Utils.getParams (req.getQueryString ()), path);
            Log.debug ("resource: "+resource);
            
            final InputStream data = resource.get ();
            
            if (data==null)
            {
                Log.error ("not found: "+path);
                resp.sendError (resp.SC_NOT_FOUND, "not found: "+path);
                return;
            }

            Utils.pipe (data, resp.getOutputStream ());
        }
    
        protected void doPut (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
        {
            final String path = req.getPathInfo ();
            Log.debug ("local PUT: "+path);
        
            final ServletContext context = getServletContext ();
            
            final Transport resource = getTransport (context, Utils.getParams (req.getQueryString ()), path);
            Log.debug ("resource: "+resource);
            
            Utils.pipe (resource.put (req.getInputStream ()), resp.getOutputStream ());
        }
    
        protected void doPatch (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
        {
            final String path = req.getPathInfo ();
            Log.debug ("local PATCH: "+path);
        
            final ServletContext context = getServletContext ();
            
            final Transport resource = getTransport (context, Utils.getParams (req.getQueryString ()), path);
            Log.debug ("resource: "+resource);
            
            Utils.pipe (resource.patch (req.getInputStream (), req.getContentType ()), resp.getOutputStream ());
        }
    
        protected void doDelete (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
        {
            final String path = req.getPathInfo ();
            Log.debug ("local DELETE: "+path);
        
            final ServletContext context = getServletContext ();
            
            final Transport resource = getTransport (context, Utils.getParams (req.getQueryString ()), path);
            Log.debug ("resource: "+resource);
            
            Utils.pipe (resource.delete (), resp.getOutputStream ());
        }
    
        protected void doPost (final HttpServletRequest req, final HttpServletResponse resp) throws IOException, ServletException
        {
            final String path = req.getPathInfo ();
            Log.debug ("local POST: "+path);
        
            final ServletContext context = getServletContext ();
            
            final Transport resource = getTransport (context, Utils.getParams (req.getQueryString ()), path);
            Log.debug ("resource: "+resource);
            
            //if (session!=null) resource.params.put (Shell.EXT_PARAM_SESSION, Arrays.asList (new String[] { session }));

            final String contentType = req.getContentType ();
            Log.debug ("request data contentType: "+contentType);
            
            Return r = resource.post (req.getInputStream (), contentType);
            Log.debug ("local post returned: "+r);
            resp.setContentType (r.type);
            if (r.disposition!=null)
            {
                resp.setHeader ("Content-Disposition", r.disposition);
            }
            Utils.pipe (r.data, resp.getOutputStream ());
        }
    }
    
    static Transport getTransport (final ServletContext context, final Map<String,List<String>> params, final String path) throws IOException
    {
        final File root = new File (context.getRealPath ("/")).getCanonicalFile ();
        return getTransport (context, params, root, root, path, path.endsWith ("/"));
    }
    
    private static Transport getTransport (final ServletContext context, final Map<String,List<String>> params, final File root, File base, String path, final boolean isDir) throws IOException
    {
        Log.debug ("getTransport () root: "+root);
        Log.debug ("getTransport () base: "+base);
        Log.debug ("getTransport () path: "+path);

        File target;
        {
            final File file;
            {
                final int sep = path.indexOf (File.separator);
                Log.debug ("sep: "+sep);
                
                if (sep<0)
                {
                    base = new File (base, path).getCanonicalFile ();
                    file = base;
                    path = "";
                }
                else if (sep==0)
                {
                    return getTransport (context, params, root, base, path.substring (1), isDir);
                    //path = path.substring (1);
                    //file = new File (base, path).getCanonicalFile ();
                }
                else
                {
                    base = new File (base, path.substring (0, sep)).getCanonicalFile ();
                    path = path.substring (sep+1);
                    file = new File (base, path).getCanonicalFile ();
                }
            }
            Log.debug ("new base: "+base);
            Log.debug ("new path: "+path);
            Log.debug ("file: "+file);

            Path fpath = base.toPath ();
            
            if (Files.isSymbolicLink (fpath))
            {
                Log.debug ("getTransport () -- found symbolic link");
                fpath = Files.readSymbolicLink (fpath);
                target = fpath.toFile ().getCanonicalFile ();
                Log.debug ("getTransport () link target: "+target);
                
                base = target;
                target = new File (base, path).getCanonicalFile ();
            }
            else
            {
                target = file;
            }
        }
        
        if (base.equals (target))
        {
            if ((target.getPath ()+File.separator).startsWith (root.getPath ()))
            {
                return new Resource ("/"+target.getPath ().substring (root.getPath ().length ()), context, params);
            }
            else
            {
                if (isDir) return new com.iovar.web.dev.trans.File (target.getPath ()+"/", context, params);
                else return new com.iovar.web.dev.trans.File (target.getPath (), context, params);
            }
        }
        else
        {
            return getTransport (context, params, root, base, path, isDir);
        }
    }
}
